import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;






import org.apache.lucene.document.Document;
import org.apache.lucene.search.ScoreDoc;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.PieChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextField;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage;


public class BatchModeUI {
	File indexFolder;
	File queryFile;
	File correctResultFile;
	PieChart chart1, chart2;
	int indexType = 0;
	int similarityType = 0;
	ArrayList<ArrayList<String>> correctResultLst = new ArrayList<ArrayList<String>>();
	ArrayList<ArrayList<String>> ansLst = new ArrayList<ArrayList<String>>();
	ArrayList<ArrayList<Integer>> feedbackLst = new ArrayList<ArrayList<Integer>>();
	ArrayList<String> queries = new ArrayList<String>();
	ArrayList<String> queryNames = new ArrayList<String>();;
	ArrayList<Double> F1 = new ArrayList<Double>();
	TextField trInput;
	Label lblF1;
	ScoreDoc[] hits;
	XYChart.Series recallData, precisionData;
	double threshold = 0.05;
	
	public BorderPane buildBatchModeUI(Stage stage){
        BorderPane border = new BorderPane();
    	VBox vbox = new VBox();
    	vbox.setPadding(new Insets(20,20,20,20));
    	
    	
    	//vbox.getChildren().addAll(addTrInput(),addIndexChooser(stage),addQueryChooser(stage), addAnswerChooser(stage), addFeedbackButton());
    	vbox.getChildren().addAll(addIndexChooser(stage),addQueryChooser(stage), addAnswerChooser(stage), addFeedbackButton());
    	border.setLeft(vbox);
    	border.setCenter(addChart());
    	
        return border;
	}
	
	private Node addTrInput() {
		VBox vbox = new VBox();
		Label label = new Label();
		
		label.setText("score threshold");
		trInput= new TextField();
    	trInput.setText(new Double(threshold).toString());
    	vbox.getChildren().addAll(label, trInput);
		return vbox;
	}

	public Button addFeedbackButton(){
    	Button btnFB = new Button("Feedback");
    	btnFB.setOnAction(new EventHandler<ActionEvent>() {
    	    @Override public void handle(ActionEvent e) {
    	    	try {
					performFeedback();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
    	    }
    	});
    	return btnFB;
	}
	
    public VBox addIndexChooser(final Stage stage){
    	
    	final Label lblIndexFolderPath = new Label("No folder selected");
    	Button button = new Button("Open folder");
    	button.setOnAction(
    			new EventHandler<ActionEvent>(){
    				public void handle(ActionEvent e){
    					indexFolder = openFolderChooser(stage);
    					if(indexFolder != null){
    						lblIndexFolderPath.setText(indexFolder.getPath());
    					}
    				}
    			});
    	Button btnIndex = new Button("Index");
    	btnIndex.setOnAction(new EventHandler<ActionEvent>() {
    	    @Override public void handle(ActionEvent e) {
    			Index indexer = new Index();
    			try {
    				if(indexFolder != null && indexFolder.exists())
    					indexer.buildIndexes(indexType, similarityType ,indexFolder.getPath());
				} catch (IOException e1) {
					e1.printStackTrace();
				}
    	    }
    	});
    	
    	final ToggleGroup AnalyzerGroup = new ToggleGroup();
    	RadioButton rbws1 = new RadioButton("Standard");
    	rbws1.setToggleGroup(AnalyzerGroup);
    	rbws1.setUserData(0);
    	rbws1.setSelected(true);
    	
    	RadioButton rbws2 = new RadioButton("Classic");
    	rbws2.setToggleGroup(AnalyzerGroup);
    	rbws2.setUserData(1);
    	
    	RadioButton rbws3 = new RadioButton("Whitespace");
    	rbws3.setToggleGroup(AnalyzerGroup);
    	rbws3.setUserData(2);
    	
    	RadioButton rbws4 = new RadioButton("Stopword");
    	rbws4.setToggleGroup(AnalyzerGroup);
    	rbws4.setUserData(3);
    	
    	AnalyzerGroup.selectedToggleProperty().addListener(new ChangeListener<Toggle>(){
    		public void changed(ObservableValue<? extends Toggle>ov,
    				Toggle toggle, Toggle new_toggle){
    			indexType = (int) AnalyzerGroup.getSelectedToggle().getUserData();
    			System.out.println("toggle weighting scheme "+indexType);
    		}
    	});
    	
    	VBox vbox = new VBox();
    	vbox.setPadding(new Insets(10,10,20,10));
    	
    	VBox wsBox = new VBox();
    	Label label1 = new Label();
    	label1.setText("Analyzer");
    	wsBox.setPadding(new Insets(10,10,20,10));
    	wsBox.getChildren().addAll(label1,rbws1, rbws2, rbws3, rbws4);
    	
    	VBox hbox = new VBox();
    	hbox.setPadding(new Insets(20,20,20,20));
    	HBox hbox2 = new HBox();
    	hbox2.setPadding(new Insets(10,10,20,10));
    	hbox2.getChildren().addAll(button, btnIndex);
    	hbox.getChildren().addAll(lblIndexFolderPath,hbox2,wsBox);
    	return hbox;
    }
    
    public VBox addQueryChooser(final Stage stage){
    	
    	final Label lblQueryFilePath = new Label("No File selected");
    	Button button = new Button("Open File");
    	button.setOnAction(
    			new EventHandler<ActionEvent>(){
    				public void handle(ActionEvent e){
    					queryFile = openFileChooser(stage);
    					if(queryFile != null){
    						lblQueryFilePath.setText(queryFile.getPath());
    					}
    				}
    			});

    	Button btnSearch = new Button("Search");
    	btnSearch.setOnAction(new EventHandler<ActionEvent>() {
    		   @Override public void handle(ActionEvent e) {
    			   try {
    				   //threshold = Double.parseDouble(trInput.getText());
					performSearch(queryFile);
				} catch (FileNotFoundException | UnsupportedEncodingException e1) {
					e1.printStackTrace();
				}
    	    }
    	});
    	
    	final ToggleGroup similarityGroup = new ToggleGroup();
    	
    	RadioButton rbsm1 = new RadioButton("Default Similarity");
    	rbsm1.setToggleGroup(similarityGroup);
    	rbsm1.setUserData(0);
    	rbsm1.setSelected(true);
    	Tooltip tt1 = new Tooltip();
    	tt1.setText(
    		    "Default Similarity Measure for lucene\n"
    		);
    	rbsm1.setTooltip(tt1);
    	
    	RadioButton rbsm2 = new RadioButton("IB Similarity");
    	rbsm2.setToggleGroup(similarityGroup);
    	rbsm2.setUserData(1);
    	Tooltip tt2 = new Tooltip();
    	tt2.setText(
    		    "information-based models\n" +
    		    "Log-logistic distribution\n" +
    		    "Computes lambda as docFreq+1 / numberOfDocuments+1\n" +
    		    "Normalization model that assumes a uniform distribution of the term frequency\n"
    		);
    	rbsm2.setTooltip(tt2);
    	
    	RadioButton rbsm3 = new RadioButton("BM25 Similarity");
    	rbsm3.setToggleGroup(similarityGroup);
    	rbsm3.setUserData(2);
    	Tooltip tt3 = new Tooltip();
    	tt3.setText(
    		    "BM25 Similarity\n"
    		);
    	rbsm3.setTooltip(tt3);
    	
    	RadioButton rbsm4 = new RadioButton("DR Similarity");
    	rbsm4.setToggleGroup(similarityGroup);
    	rbsm4.setUserData(3);
    	Tooltip tt4 = new Tooltip();
    	tt4.setText(
    		    "divergence from randomness similarity measure\n" +
    		    "with Bernoulli processes aftereffect\n" +
    		    "Limiting form of the Bose-Einstein model\n" +
    		    "Normalization model that assumes a uniform distribution of the term frequency\n"
    		);
    	rbsm4.setTooltip(tt4);
    	

    	
    	similarityGroup.selectedToggleProperty().addListener(new ChangeListener<Toggle>(){
    		public void changed(ObservableValue<? extends Toggle>ov,
    				Toggle toggle, Toggle new_toggle){
    			similarityType = (int) similarityGroup.getSelectedToggle().getUserData();
    			System.out.println("toggle similarity measure "+similarityType);
    		}
    	});
    		
    	VBox smBox = new VBox();
    	Label label2 = new Label();
    	label2.setText("Similarity Measures");
    	smBox.setPadding(new Insets(10,10,20,10));
    	smBox.getChildren().addAll(label2, rbsm1, rbsm2, rbsm3, rbsm4);
    	
    	VBox hbox = new VBox();
    	hbox.setPadding(new Insets(20,20,20,20));

    	HBox hbox2 = new HBox();
    	hbox2.setPadding(new Insets(10,10,20,10));
    	hbox2.getChildren().addAll(button, btnSearch);
    	
    	hbox.getChildren().addAll(lblQueryFilePath, hbox2, smBox);
    	return hbox;
    }
    
    public VBox addAnswerChooser(final Stage stage){
    	
    	final Label lblResultFilePath = new Label("No File selected");
    	Button button = new Button("Open File");
    	button.setOnAction(
    			new EventHandler<ActionEvent>(){
    				public void handle(ActionEvent e){
    					correctResultFile = openFileChooser(stage);
    					if(correctResultFile != null){
    						lblResultFilePath.setText(correctResultFile.getPath());
    					}
    				}
    			});

    	Button btnLoad = new Button("Load Answer");
    	btnLoad.setOnAction(new EventHandler<ActionEvent>() {
    		   @Override public void handle(ActionEvent e) {
    			   System.out.println("Start to load correct answer");
    			   loadAnswer(correctResultFile);
    	    }
    	});
    	
    	VBox hbox = new VBox();
    	hbox.setPadding(new Insets(20,20,20,20));
    	HBox hbox2 = new HBox();
    	hbox2.getChildren().addAll(button, btnLoad);
    	hbox.getChildren().addAll(lblResultFilePath,hbox2);
    	return hbox;
    }
    
    public File openFolderChooser(Stage stage){
    	DirectoryChooser fc = new DirectoryChooser();
    	fc.setTitle("Open File");
    	File file = fc.showDialog(stage);
    	return file;
    }
    
    public File openFileChooser(Stage stage){
    	FileChooser fc = new FileChooser();
    	fc.setTitle("Open File");
    	File file = fc.showOpenDialog(stage);
    	return file;
    }
    
    public FlowPane addChart(){
    	FlowPane flow = new FlowPane();
    	flow.setPadding(new Insets(5,0,5,0));
    	flow.setVgap(4);
    	flow.setHgap(4);
    	flow.setPrefWrapLength(170);
    	
    	final CategoryAxis xAxis = new CategoryAxis();
    	final NumberAxis yAxis = new NumberAxis();
    	final BarChart<String, Number> bc1 = new BarChart<String, Number>(xAxis, yAxis);
    	bc1.setTitle("Recall & Precision");
    	xAxis.setLabel("Query");
    	yAxis.setLabel("Recall");
    	
    	recallData  = new XYChart.Series();
    	recallData.setName("Recall");
    	
    	precisionData = new XYChart.Series();
    	precisionData.setName("Precision");
    	
    	bc1.getData().addAll(recallData, precisionData);
    	bc1.setMaxWidth(450);
    	lblF1 = new Label();
	
        flow.getChildren().addAll(bc1, lblF1);
    	
    	return flow;
    }
    
    public void updateChart(){
    	recallData.getData().clear();
    	precisionData.getData().clear();
    	F1.clear();
    	for(int i = 0; i < queryNames.size(); i++){
    		double recall = computeRecall(i);
    		double precision = computePrecision(i);
    		if ((precision + recall)!=0){
	    		Double value = (2*precision*recall / (precision + recall));
	    		DecimalFormat threeDForm = new DecimalFormat("#.###");
	    		F1.add(Double.valueOf(threeDForm.format(value)));
    		}
    		else {
    			F1.add(0.000);
    		}
	    	recallData.getData().add(new XYChart.Data(queryNames.get(i), recall));
	    	precisionData.getData().add(new XYChart.Data(queryNames.get(i), precision));
    		
    	}
    	String txtF1 = "";
    	for(int i = 0; i < F1.size();i++){
    		txtF1 += " " + queryNames.get(i) +": " + F1.get(i) +" ";
    		if(i == 6)
    			txtF1+="\n";
    	}
    	lblF1.setText(txtF1);
    }
    
    
    public void readQueryFile(File queryFile){
    	System.out.println("reading query file...");
    	// read query file
    	try {
    		queryNames = new ArrayList<String>();
    		queries = new ArrayList<String>();
			BufferedReader br = new BufferedReader(new FileReader(queryFile));
			StringBuilder sb = new StringBuilder();
			String line = br.readLine();
			System.out.println(line);
			String query = "";
			boolean queryStart = false;
			while (line != null){
				String[] tokens = line.split("\\s+");
				if(tokens.length>0 && tokens[0].equals("<DOCNO>")){
					queryNames.add(tokens[1]);
					queryStart = true;
					query = "";
				}else if(tokens.length>0 && tokens[0].equals("</DOC>")){
					queryStart = false;
					queries.add(query);
				}else if (queryStart){
					query += line;
				}
				line = br.readLine();
			}
    	}catch(Exception e){
    		e.printStackTrace();
    	}
    }
    
    public void startSearching(){
    	System.out.println("searching start...");
    	ansLst = new ArrayList<ArrayList<String>>();
    	feedbackLst = new ArrayList<ArrayList<Integer>>();
    	for(int i = 0; i < queries.size(); i++){
    		System.out.println("searching "+queryNames.get(i));
    		ArrayList<String>correctAns = correctResultLst.get(i);
    		ArrayList<Integer>feedbackAns = new ArrayList<Integer>();
    		ArrayList<String>ans = new ArrayList<String>();
	    	try {
				Search searcher = new Search();
				hits = searcher.performSearch(queries.get(i), 15, similarityType, 0);
				for (int j = 0; j < hits.length; j++) {
					//if (hits[j].score>threshold) {
						Document doc = searcher.searcher.doc(hits[j].doc); 
						ans.add(doc.get("name"));
						if(correctAns.contains(doc.get("name"))){
							feedbackAns.add(hits[j].doc);
						}
					//}
				}
				ansLst.add(ans);
				feedbackLst.add(feedbackAns);
			} catch (Exception e1) {
				e1.printStackTrace();
			}
    	}
    	updateChart();
    }
    public void performSearch(File queryFile) throws FileNotFoundException, UnsupportedEncodingException{

    	readQueryFile(queryFile);
    	// search start
    	startSearching();
    	
    	// write ans to file
    	writeAnsToFile();
    }
    public void performFeedback() throws IOException{
    	System.out.println("start feedback...");
    	RelevantFeedBack rf = new RelevantFeedBack();
    	ArrayList<String> tempQueries = new ArrayList<String>();
    	for(int i=0; i < feedbackLst.size(); i++){
    		tempQueries.add(queries.get(i)+rf.performRelevantFeedBack(feedbackLst.get(i)));
    	}
    	queries = tempQueries;
    	startSearching();
    }
    
    public void loadAnswer(File correctResultFile){
    	correctResultLst = new ArrayList<ArrayList<String>>();
    	String curQuery = "";
    	ArrayList<String> curAnsLst = new ArrayList<String>();
    	try {
			BufferedReader br = new BufferedReader(new FileReader(correctResultFile));
			StringBuilder sb = new StringBuilder();
			String line = br.readLine();
			while (line != null){
				String[] tokens = line.split("\\s+");
				if(tokens[0].equals(curQuery)){
					curAnsLst.add(tokens[1]);
				}else{
					if(curAnsLst.size() != 0)
						correctResultLst.add(curAnsLst);

					curQuery = tokens[0];
					curAnsLst = new ArrayList<String>();
					curAnsLst.add(tokens[1]);
				}
				line = br.readLine();
			}
			System.out.println("Load answer succeed!");
    	}catch(Exception e){
    		e.printStackTrace();
    	}
    	// add in the last anslist
    	correctResultLst.add(curAnsLst);
    }
    
    public void writeAnsToFile() throws FileNotFoundException, UnsupportedEncodingException{
    	PrintWriter pw = new PrintWriter("output.txt","UTF-8");
    	for(int i = 0; i < ansLst.size(); i++){
    		ArrayList<String> ans = ansLst.get(i);
    		Collections.sort(ans);
    		for(int j = 0; j < ans.size(); j++){
    			pw.println("q"+j+" "+ans.get(j));
    		}
    	}
    }
    
    public double computeRecall(int num){
    	ArrayList<String> correctResult = correctResultLst.get(num);
    	int expectedSize = correctResult.size();
    	ArrayList<String> ans = ansLst.get(num);    	
    	int matchCount = 0;
    	for(String i : correctResult){
    		if(ans.contains(i)){
    			matchCount ++;
    		}
    	}
    	return (double)matchCount / (double)expectedSize;
    }
    
    public double computePrecision(int num){
    	ArrayList<String> correctResult = correctResultLst.get(num);
    	ArrayList<String> ans = ansLst.get(num);    
    	int expectedSize = ans.size();
    	int matchCount = 0;
    	for(String i : ans){
    		if(correctResult.contains(i)){
    			matchCount ++;
    		}
    	}
    	if (expectedSize!=0)
    		return (double)matchCount / (double)expectedSize;
    	else
    		return 0;
    }
    

}
